/**
 * ****************************************************************************
 * Project: RoboBridge
 * ----------------------------------------------------------------------------
 * -------------------------- [c]2012 - KrawallMann ---------------------------
 * ---------------------- http://www.krawallmann.de.tc/ -----------------------
 * ****************************************************************************
 * File: Command.java Package: tc.de.krawallmann.robobridge.communication
 * Version: 1.0 Author(s): KrawallMann
 * ****************************************************************************
 * Description:
 * ****************************************************************************
 */
package tc.de.krawallmann.robobridge.communication;

import java.util.Arrays;

import tc.de.krawallmann.robobridge.util.Exceptions;

/**
 * The Class Command is used for communicating with the robot.
 * 
 * @author KrawallMann
 * @version 1.0
 */
public class Command {

	/**
	 * Parses the Command from the given String.
	 * 
	 * @param serialized
	 *            the serialized
	 * @return the command
	 */
	public static Command parse(final String serialized) {
		//TODO: Neues Protokoll?
		if (serialized == null) {
			throw Exceptions.onNullPointer(Command.class, "serialized");
		}
		String serializedWorkingCopy = serialized.replace("\r", "").replace("\n", ""); // Replace
		// line end chars
		{
			// Find the start
			int cursor = 0;
			while (serializedWorkingCopy.charAt(cursor) != '<'
					&& cursor < serializedWorkingCopy.length() - 1) {
				++cursor;
			}
			if (cursor != 0) {
				serializedWorkingCopy = serializedWorkingCopy.substring(cursor);
			}
			if (serializedWorkingCopy.length() < 4) {
				throw new IllegalArgumentException(
						"The received String is too short to contain a valid command: \""
								+ serialized + "\"");
			}
			// Find the end
			cursor = serializedWorkingCopy.length() - 1;
			while (serializedWorkingCopy.charAt(cursor) != '$'
					&& serializedWorkingCopy.charAt(cursor - 1) != '%' && cursor > 2) {
				--cursor;
			}
			if (cursor != serializedWorkingCopy.length() - 1) {
				serializedWorkingCopy = serializedWorkingCopy.substring(0, cursor);
			}
			// Check
			if (serializedWorkingCopy.charAt(0) != '<' || !serializedWorkingCopy.contains("%$")) {
				System.out.println(serialized.replace((char)0, '0').replaceAll("\r", "r").replaceAll("\n", "n"));
				for(int i=0;i<serialized.length();++i){
					System.out.print((int)serialized.charAt(i)+" ");
				}
				System.out.println();
				throw new IllegalArgumentException(
						"The received String does not contain a valid command sequence: \""
								+ serialized + "\"");
			}

			// Cut the start and end away
			serializedWorkingCopy =
					serializedWorkingCopy.substring(1, serializedWorkingCopy.length() - 2);
			final String[] args = serializedWorkingCopy.split("&");
			if (args.length == 0) {
				try {
					return new Command(CommandType.get(Integer.parseInt(serializedWorkingCopy)),
							false);
				} catch (final NumberFormatException e) {
					throw new IllegalArgumentException(
							"The received String does not contain a valid command sequence: \""
									+ serialized + "\"", e);
				}
			}
			try {
				final int commandId = Integer.parseInt(args[0]);
				final Object[] params = new Object[args.length - 1];
				for (cursor = 1; cursor < args.length; ++cursor) {
					Object obj;
					switch (args[cursor].charAt(0)) {
						case 'i': {
							obj = Integer.parseInt(args[cursor].substring(1));
							break;
						}
						case 's': {
							try {
								final int length =
										Integer.parseInt(serializedWorkingCopy.substring(0,
												serializedWorkingCopy.indexOf('=')));
								final String ret =
										serializedWorkingCopy.substring(serializedWorkingCopy
												.indexOf('=') + 1);
								if (ret.length() != length) {
									throw new IllegalArgumentException("Invalid string length: "
											+ length + " (is): " + ret.length() + " in "
											+ serialized);
								}
								obj = ret;
							} catch (final NumberFormatException e) {
								throw new IllegalArgumentException("Not a String: "
										+ serialized, e);
							}
							break;
						}
						default: {
							
							throw new IllegalArgumentException(
									"The received String does not contain a valid command sequence or contains unsupported types: \""
											+ serialized + "\"");
						}
					}
					params[cursor - 1] = obj;
				}
				return new Command(CommandType.get(commandId), false, params);
			} catch (final NumberFormatException e) {
				System.out.println(serialized.replace((char)0, '0').replaceAll("\r", "r").replaceAll("\n", "n"));
				for(int i=0;i<serialized.length();++i){
					System.out.print((int)serialized.charAt(i)+" ");
				}
				System.out.println();
				throw new IllegalArgumentException(
						"The received String does not contain a valid command sequence: "+serialized, e);
			}
		}
	}

	/** The ack required. */
	private transient final boolean ackRequired;

	/** The parameters. */
	private transient final Object[] parameters;

	/** The type. */
	private transient final CommandType type;

	/**
	 * Instantiates a new command.
	 * 
	 * @param type
	 *            the type
	 * @param ackRequired
	 *            the ack required
	 * @param parameters
	 *            the parameters
	 */
	public Command(final CommandType type, final boolean ackRequired, final Object... parameters) {
		super();
		if (type == null) {
			throw Exceptions.onNullPointer(this, "type");
		}
		this.type = type;
		this.ackRequired = ackRequired;
		if (parameters == null) {
			this.parameters = new Object[0];
		} else {
			this.parameters = parameters;
			for (int i = 0; i < parameters.length; ++i) {
				if (parameters[i] == null) {
					throw Exceptions.onNullPointer(this, "parameters[" + i + "]");
				} else if (parameters[i].getClass() != Integer.class
						&& parameters[i].getClass() != String.class) {
					throw new IllegalArgumentException("Parameter " + i
							+ " must be of type Integer or String!");
				}
			}
		}
	}

	/**
	 * Gets the parameter.
	 * 
	 * @param <T>
	 *            the generic type
	 * @param index
	 *            the index
	 * @return the parameter
	 */
	@SuppressWarnings("unchecked")
	public <T> T getParameter(final int index) {
		if (index < 0 || index >= parameters.length) {
			throw new IllegalArgumentException("Index out of bounds [0," + (parameters.length - 1)
					+ "]");
		}
		T ret;
		try {
			ret = (T) parameters[index];
		} catch (final ClassCastException exc) {
			throw new IllegalArgumentException("Cannot cast this parameter to the given type!", exc);
		}
		return ret;
	}

	/**
	 * Gets the parameter class.
	 * 
	 * @param index
	 *            the index
	 * @return the parameter class
	 */
	public Class<?> getParameterClass(final int index) {
		if (index < 0 || index >= parameters.length) {
			throw new IllegalArgumentException("Index out of bounds [0," + (parameters.length - 1)
					+ "]");
		}
		return parameters[index].getClass();
	}

	/**
	 * Gets the parameters.
	 * 
	 * @return the parameters
	 */
	@SuppressWarnings("PMD.MethodReturnsInternalArray")
	public Object[] getParameters() {
		return parameters;
	}

	/**
	 * Gets the type.
	 * 
	 * @return the type
	 */
	public CommandType getType() {
		return type;
	}

	/**
	 * Checks if is ack required.
	 * 
	 * @return true, if is ack required
	 */
	public boolean isAckRequired() {
		return ackRequired;
	}

	/**
	 * Serialize.
	 * 
	 * @return the string
	 */
	public String serialize() {
		final StringBuilder sbuild = new StringBuilder(20);
		sbuild.append('<');
		sbuild.append(type.getCommandId());
		for (final Object o : parameters) {
			if (Integer.class.isAssignableFrom(o.getClass())) {
				sbuild.append('&');
				sbuild.append('i');
				sbuild.append(o);
			} else if (String.class.isAssignableFrom(o.getClass())) {
				final String str = (String) o;
				sbuild.append('&');
				sbuild.append('s');
				sbuild.append(str.length());
				sbuild.append('=');
				sbuild.append(str);
			}
		}
		sbuild.append("%$\n");
		return sbuild.toString();
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Command [type=" + type + "(" + type.getCommandId() + ")" + ", ackRequired="
				+ ackRequired + ", parameters=" + Arrays.toString(parameters) + "]";
	}
}
